import fs from 'fs'
import { parse } from '@babel/parser'
import traverse from '@babel/traverse'
import path from 'path'
import ejs from 'ejs'
import { transformFromAst } from 'babel-core'
import webpackConfig from './webpackConfig.js'
import { SyncHook } from 'tapable'

let id = 0
const hooks = {
	emitFile: new SyncHook(['context']),
}
/* 
    webapck 静态模块打包工具 当它处理程序时，它会在内部从一个或者多个入口构建一个依赖关系
    然后将你项目中需要的模块组合成一个或多个boundls
*/

const createAsset = (path) => {
	// 1.获取文件内容
	let source = fs.readFileSync(path, 'utf-8')
	const { rules } = webpackConfig.module
	const loaderContext = {
		addDeps(dep) {
			console.log('addDeps :>>', dep)
		},
	}
	rules.forEach(({ test, use }) => {
		if (test.test(path)) {
			if (Array.isArray(use)) {
				// loader 是从下往上执行的
				use.reverse().forEach((fn) => {
					source = fn.call(loaderContext, source)
				})
			} else if (typeof use === 'function') {
				source = use(loaderContext, source)
			}
		}
	})
	// console.log('source :>>', source)
	// 2.获取依赖关系 ast => 抽象语法树
	const ast = parse(source, { sourceType: 'module' })
	const deps = []
	traverse.default(ast, {
		ImportDeclaration({ node: { source } }) {
			// console.log('ImportDeclaration  :>>', source.value)
			deps.push(source.value)
		},
	})
	const { code } = transformFromAst(ast, null, {
		presets: ['env'], // npm i babel-preset-env
	})
	// console.log('transformFromAst :>>', code)
	return {
		path,
		code,
		deps,
		id: id++,
		mapping: {},
	}
}

function createGraph() {
	const mainAsset = createAsset('./src/main.js')
	// 广度优先搜索
	const queue = [mainAsset]
	for (const asset of queue) {
		asset.deps.forEach((relativePath) => {
			const child = createAsset(path.resolve('./src', relativePath))
			asset.mapping[relativePath] = child.id
			// console.log('child :>>', child)
			queue.push(child)
		})
	}
	return queue
}
function initPlugins() {
	const { plugins } = webpackConfig
	plugins.forEach((plugin) => {
		plugin.apply(hooks)
	})
}
initPlugins()
const graph = createGraph()
// console.log('graph :>>', graph)

function bundle(graph) {
	const template = fs.readFileSync('./bundle.ejs', 'utf-8')
	const data = graph.map(({ mapping, code, id }) => ({ id, mapping, code }))
	const code = ejs.render(template, { data })
	let path = './dist/bundle.js'
	// 触发
	const context = {
		changeOutputPath(newPath) {
			path = newPath
		},
	}
	hooks.emitFile.call(context)
	fs.writeFileSync(path, code)
	// console.log('code :>>', code)
}
bundle(graph)
